1use std::ffi::CString;
2use std::sync::Arc;
3use std::{ffi::CStr, ptr};
4
5use anyhow::bail;
6use getters0::Getters;
7use libobs::{
8 audio_output, obs_encoder_set_audio, obs_encoder_set_video, obs_output, obs_output_active,
9 obs_output_create, obs_output_get_last_error, obs_output_release, obs_output_set_audio_encoder,
10 obs_output_set_video_encoder, obs_output_start, obs_output_stop, obs_output_update,
11 video_output,
12};
13
14use crate::enums::ObsOutputStopSignal;
15use crate::runtime::ObsRuntime;
16use crate::unsafe_send::Sendable;
17use crate::utils::async_sync::RwLock;
18use crate::utils::{AudioEncoderInfo, OutputInfo, VideoEncoderInfo};
19use crate::{impl_obs_drop, impl_signal_manager, run_with_obs, rx_recv};
20
21use crate::{
22 encoders::{audio::ObsAudioEncoder, video::ObsVideoEncoder},
23 utils::{ObsError, ObsString},
24};
25
26use super::ObsData;
27
28mod replay_buffer;
29pub use replay_buffer::*;
30
31#[derive(Debug)]
32struct _ObsDropGuard {
33 output: Sendable<*mut obs_output>,
34 runtime: ObsRuntime,
35}
36
37impl_obs_drop!(_ObsDropGuard, (output), move || unsafe {
38 obs_output_release(output);
39});
40
41#[derive(Debug, Getters, Clone)]
42#[skip_new]
43pub struct ObsOutputRef {
54 pub(crate) settings: Arc<RwLock<Option<ObsData>>>,
56
57 pub(crate) hotkey_data: Arc<RwLock<Option<ObsData>>>,
59
60 #[get_mut]
62 pub(crate) video_encoders: Arc<RwLock<Vec<Arc<ObsVideoEncoder>>>>,
63
64 #[get_mut]
66 pub(crate) audio_encoders: Arc<RwLock<Vec<Arc<ObsAudioEncoder>>>>,
67
68 #[skip_getter]
70 pub(crate) output: Sendable<*mut obs_output>,
71
72 pub(crate) id: ObsString,
74
75 pub(crate) name: ObsString,
77
78 #[skip_getter]
80 _drop_guard: Arc<_ObsDropGuard>,
81
82 #[skip_getter]
83 pub(crate) runtime: ObsRuntime,
84
85 pub(crate) signal_manager: Arc<ObsOutputSignals>,
86}
87
88impl ObsOutputRef {
89 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
90 pub(crate) async fn new(output: OutputInfo, runtime: ObsRuntime) -> Result<Self, ObsError> {
99 let (output, id, name, settings, hotkey_data) = runtime
100 .run_with_obs_result(|| {
101 let OutputInfo {
102 id,
103 name,
104 settings,
105 hotkey_data,
106 } = output;
107
108 let settings_ptr = match settings.as_ref() {
109 Some(x) => x.as_ptr(),
110 None => Sendable(ptr::null_mut()),
111 };
112
113 let hotkey_data_ptr = match hotkey_data.as_ref() {
114 Some(x) => x.as_ptr(),
115 None => Sendable(ptr::null_mut()),
116 };
117
118 let output = unsafe {
119 obs_output_create(
120 id.as_ptr().0,
121 name.as_ptr().0,
122 settings_ptr.0,
123 hotkey_data_ptr.0,
124 )
125 };
126
127 if output == ptr::null_mut() {
128 bail!("Null pointer returned from obs_output_create");
129 }
130
131 return Ok((Sendable(output), id, name, settings, hotkey_data));
132 })
133 .await
134 .map_err(|e| ObsError::InvocationError(e.to_string()))?
135 .map_err(|_| ObsError::NullPointer)?;
136
137 let signal_manager = ObsOutputSignals::new(&output, runtime.clone()).await?;
138 Ok(Self {
139 settings: Arc::new(RwLock::new(settings)),
140 hotkey_data: Arc::new(RwLock::new(hotkey_data)),
141
142 video_encoders: Arc::new(RwLock::new(vec![])),
143 audio_encoders: Arc::new(RwLock::new(vec![])),
144
145 output: output.clone(),
146 id,
147 name,
148
149 _drop_guard: Arc::new(_ObsDropGuard {
150 output,
151 runtime: runtime.clone(),
152 }),
153
154 runtime,
155 signal_manager: Arc::new(signal_manager),
156 })
157 }
158
159 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
160 pub async fn get_video_encoders(&self) -> Vec<Arc<ObsVideoEncoder>> {
165 self.video_encoders.read().await.clone()
166 }
167
168 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
169 pub async fn video_encoder(
181 &mut self,
182 info: VideoEncoderInfo,
183 handler: Sendable<*mut video_output>,
184 ) -> Result<Arc<ObsVideoEncoder>, ObsError> {
185 let video_enc = ObsVideoEncoder::new(
186 info.id,
187 info.name,
188 info.settings,
189 info.hotkey_data,
190 self.runtime.clone(),
191 )
192 .await?;
193
194 let encoder_ptr = video_enc.encoder.clone();
195 let output_ptr = self.output.clone();
196 let handler = Sendable(handler);
197
198 run_with_obs!(
199 self.runtime,
200 (encoder_ptr, output_ptr, handler),
201 move || unsafe {
202 obs_encoder_set_video(encoder_ptr, handler.0);
203 obs_output_set_video_encoder(output_ptr, encoder_ptr);
204 }
205 )
206 .await?;
207
208 let tmp = Arc::new(video_enc);
209 self.video_encoders.write().await.push(tmp.clone());
210
211 Ok(tmp)
212 }
213
214 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
215 pub async fn set_video_encoder(&mut self, encoder: ObsVideoEncoder) -> Result<(), ObsError> {
223 if encoder.encoder.0.is_null() {
224 return Err(ObsError::NullPointer);
225 }
226
227 let output = self.output.clone();
228 let encoder_ptr = encoder.as_ptr();
229
230 run_with_obs!(self.runtime, (output, encoder_ptr), move || unsafe {
231 obs_output_set_video_encoder(output, encoder_ptr);
232 })
233 .await?;
234
235 if !self
236 .video_encoders
237 .read()
238 .await
239 .iter()
240 .any(|x| x.encoder.0 == encoder.as_ptr().0)
241 {
242 let tmp = Arc::new(encoder);
243
244 self.video_encoders.write().await.push(tmp.clone());
245 }
246
247 Ok(())
248 }
249
250 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
251 pub async fn update_settings(&mut self, settings: ObsData) -> Result<(), ObsError> {
261 let output = self.output.clone();
262 let output_active = run_with_obs!(self.runtime, (output), move || unsafe {
263 obs_output_active(output)
264 })
265 .await?;
266
267 if !output_active {
268 let settings_ptr = settings.as_ptr();
269
270 run_with_obs!(self.runtime, (output, settings_ptr), move || unsafe {
271 obs_output_update(output, settings_ptr)
272 })
273 .await?;
274
275 self.settings.write().await.replace(settings);
276 Ok(())
277 } else {
278 Err(ObsError::OutputAlreadyActive)
279 }
280 }
281
282 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
283 pub async fn audio_encoder(
296 &mut self,
297 info: AudioEncoderInfo,
298 mixer_idx: usize,
299 handler: Sendable<*mut audio_output>,
300 ) -> Result<Arc<ObsAudioEncoder>, ObsError> {
301 let audio_enc = ObsAudioEncoder::new(
302 info.id,
303 info.name,
304 info.settings,
305 mixer_idx,
306 info.hotkey_data,
307 self.runtime.clone(),
308 )
309 .await?;
310
311 let encoder_ptr = audio_enc.encoder.clone();
312 let output_ptr = self.output.clone();
313
314 run_with_obs!(
315 self.runtime,
316 (handler, encoder_ptr, output_ptr),
317 move || unsafe {
318 obs_encoder_set_audio(encoder_ptr, handler);
319 obs_output_set_audio_encoder(output_ptr, encoder_ptr, mixer_idx);
320 }
321 )
322 .await?;
323
324 let x = Arc::new(audio_enc);
325 self.audio_encoders.write().await.push(x.clone());
326 Ok(x)
327 }
328
329 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
330 pub async fn set_audio_encoder(
339 &mut self,
340 encoder: ObsAudioEncoder,
341 mixer_idx: usize,
342 ) -> Result<(), ObsError> {
343 if encoder.encoder.0.is_null() {
344 return Err(ObsError::NullPointer);
345 }
346
347 let encoder_ptr = encoder.encoder.clone();
348 let output_ptr = self.output.clone();
349 run_with_obs!(self.runtime, (output_ptr, encoder_ptr), move || unsafe {
350 obs_output_set_audio_encoder(output_ptr, encoder_ptr, mixer_idx)
351 })
352 .await?;
353
354 if !self
355 .audio_encoders
356 .read()
357 .await
358 .iter()
359 .any(|x| x.encoder.0 == encoder.encoder.0)
360 {
361 let tmp = Arc::new(encoder);
362 self.audio_encoders.write().await.push(tmp.clone());
363 }
364
365 Ok(())
366 }
367
368 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
369 pub async fn start(&self) -> Result<(), ObsError> {
376 let output_ptr = self.output.clone();
377 let output_active = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
378 obs_output_active(output_ptr)
379 })
380 .await?;
381
382 if !output_active {
383 let res = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
384 obs_output_start(output_ptr)
385 })
386 .await?;
387
388 if res {
389 return Ok(());
390 }
391
392 let err = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
393 Sendable(obs_output_get_last_error(output_ptr))
394 })
395 .await?;
396
397 let c_str = unsafe { CStr::from_ptr(err.0) };
398 let err_str = c_str.to_str().ok().map(|x| x.to_string());
399
400 return Err(ObsError::OutputStartFailure(err_str));
401 }
402
403 Err(ObsError::OutputAlreadyActive)
404 }
405
406 #[cfg_attr(feature = "blocking", remove_async_await::remove_async_await)]
414 pub async fn stop(&mut self) -> Result<(), ObsError> {
415 let output_ptr = self.output.clone();
416 let output_active = run_with_obs!(self.runtime, (output_ptr), move || unsafe {
417 obs_output_active(output_ptr)
418 })
419 .await?;
420
421 if output_active {
422 let mut rx = self.signal_manager.on_stop().await?;
423 run_with_obs!(self.runtime, (output_ptr), move || unsafe {
424 obs_output_stop(output_ptr)
425 })
426 .await?;
427
428 let signal = rx_recv!(rx).map_err(|_| ObsError::NoSenderError)?;
429 log::debug!("Signal: {:?}", signal);
430 if signal == ObsOutputStopSignal::Success {
431 return Ok(());
432 }
433
434 return Err(ObsError::OutputStopFailure(Some(signal.to_string())));
435 }
436
437 return Err(ObsError::OutputStopFailure(Some(
438 "Output is not active.".to_string(),
439 )));
440 }
441
442 pub fn as_ptr(&self) -> Sendable<*mut obs_output> {
443 self.output.clone()
444 }
445}
446
447pub unsafe fn process_stop_signal(
448 cd: *mut libobs::calldata_t,
449) -> anyhow::Result<ObsOutputStopSignal> {
450 let mut code = 0i64;
451 let code_str = CString::new("code").unwrap();
452 let got_code = libobs::calldata_get_data(
453 cd,
454 code_str.as_ptr(),
455 &mut code as *mut _ as *mut std::ffi::c_void,
456 size_of::<i64>(),
457 );
458
459 if !got_code {
460 bail!("Failed to get code from calldata");
461 }
462
463 let signal = ObsOutputStopSignal::try_from(code as i32);
464 if let Err(e) = signal {
465 bail!("Failed to convert code to ObsOutputStopSignal: {}", e);
466 }
467
468 Ok(signal.unwrap())
469}
470
471impl_signal_manager!(|ptr| libobs::obs_output_get_signal_handler(ptr), ObsOutputSignals for ObsOutputRef<*mut libobs::obs_output>, [
472 "start": {},
473 "stop": {code: crate::enums::ObsOutputStopSignal},
474 "pause": {},
475 "unpause": {},
476 "starting": {},
477 "stopping": {},
478 "activate": {},
479 "deactivate": {},
480 "reconnect": {},
481 "reconnect_success": {},
482]);